﻿using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using System.IO;
using System.Threading;
using System.Runtime.InteropServices;

namespace Ashutosh.PictureArrangerDateWise
{
    public partial class PictureArranger : Form
    {
        readonly Dictionary<string, DateTime> _fileMap = new Dictionary<string, DateTime>();
        bool recurse;
        bool cancel;
        string startFolder = "";
        string outputFolder = "";
        string imageFilePrefix = "";
        bool createHardLink;
        private bool processFilesWithoutDate = true;
        private bool usePictureFileModifiedTimeAsDefault = true;
        private static readonly string[] ImageExtensions = new string[]
                                                               {
                                                                   ".bmp", ".jpg", ".jpeg", ".gif",
                                                                   ".png", ".jpeg", ".tif", ".tiff"
                                                               };

        private Thread workerThread;
        private bool terminating;
        delegate void UpdateStatusDelegate(int progress);

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes);

        public bool ControlsEnabled
        {
            set
            {
                pictureFolderTextBox.Enabled =
                    fileNamePrefixTextBox.Enabled =
                    outputFolderTextBox.Enabled =
                    recurseCheckBox.Enabled =
                    browsePictureFolderButton.Enabled =
                    browseTargetFolderButton.Enabled = 
                    createHardLinkCheckBox.Enabled =
                    startButton.Visible = value;
                processPictureWithoutDateInfo.Enabled = value;

                defaultFileModifedDate.Enabled =
                defaultCurrentDate.Enabled = processPictureWithoutDateInfo.Checked & value;
                cancelButton.Visible = !value;
            }
        }

        public PictureArranger()
        {
            InitializeComponent();
        }

        private void browsePictureFolderButton_Click(object sender, EventArgs e)
        {
            FolderBrowserDialog dlg = new FolderBrowserDialog();
            dlg.SelectedPath = pictureFolderTextBox.Text;
            dlg.Description = @"Select the folder containing pictures you want to rename based on date/time";
            if (dlg.ShowDialog() != DialogResult.OK) return;
            pictureFolderTextBox.Text = dlg.SelectedPath;
        }


        private void browseTargetFolderButton_Click(object sender, EventArgs e)
        {
            FolderBrowserDialog dlg = new FolderBrowserDialog();
            dlg.SelectedPath = outputFolderTextBox.Text;
            dlg.Description = @"Select the folder where you want to same the renamed images";
            if (dlg.ShowDialog() != DialogResult.OK) return;
            outputFolderTextBox.Text = dlg.SelectedPath;
        }
        
        private void pictureFolderTextBox_TextChanged(object sender, EventArgs e)
        {
            startButton.Enabled = !String.IsNullOrEmpty(pictureFolderTextBox.Text)
                && Directory.Exists(pictureFolderTextBox.Text)
                && !String.IsNullOrEmpty(outputFolderTextBox.Text)
                && Directory.Exists(outputFolderTextBox.Text);

            if (startButton.Enabled)
            {
                createHardLinkCheckBox.Enabled = String.Compare(Path.GetPathRoot(pictureFolderTextBox.Text), Path.GetPathRoot(outputFolderTextBox.Text), StringComparison.OrdinalIgnoreCase) == 0;
            }
            else
            {
                createHardLinkCheckBox.Enabled = false;
            }
        }

        private void startButton_Click(object sender, EventArgs e)
        {
            if (String.Compare(pictureFolderTextBox.Text, outputFolderTextBox.Text, StringComparison.OrdinalIgnoreCase) == 0
                || pictureFolderTextBox.Text.StartsWith(outputFolderTextBox.Text, StringComparison.OrdinalIgnoreCase))
            {
                MessageBox.Show("Output folder should be different from source folder", 
                    "Picture Arranger", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            cancel = false;
            ControlsEnabled = false;

            startFolder = pictureFolderTextBox.Text;
            outputFolder = outputFolderTextBox.Text;
            imageFilePrefix = fileNamePrefixTextBox.Text;
            recurse = recurseCheckBox.Checked;

            createHardLink = createHardLinkCheckBox.Enabled && createHardLinkCheckBox.Checked;
            usePictureFileModifiedTimeAsDefault = defaultFileModifedDate.Checked;

            workerThread = new Thread(WorkerThread);
            workerThread.Start();
        }

        private void cancelButton_Click(object sender, EventArgs e)
        {
            cancel = true;
        }

        void WorkerThread()
        {
            FindFiles(new DirectoryInfo(startFolder));

            int k = 0;
            int initialCount = _fileMap.Count;
            foreach (var item in _fileMap.Keys.ToList())
            {
                if (cancel == true)
                {
                    UpdateStatus(-1);
                    return;
                }
                var date = GetDates(item); ;
                if(null != date)
                {
                    _fileMap[item] = date.Value;
                }
                else
                {
                    _fileMap.Remove(item);
                }
                k++;
                int percent = (k * 100) / (initialCount * 2);
                UpdateStatus(percent);
            }
            Debug.WriteLine("File date map created.");
            UpdateStatus(50);

            var files = _fileMap.OrderBy(p => p.Value).Select(p => p.Key).ToList();
            for (int i = 1; i <= files.Count && cancel == false; i++)
            {
                string sourceFile = files[i - 1];
                string extension = Path.GetExtension(sourceFile);

                string targetFile = Path.Combine(outputFolder, String.Format("{0}{1}{2}", imageFilePrefix, i.ToString("00000"),extension));
                if (createHardLink)
                {
                    if (!CreateHardLink(targetFile, files[i - 1], IntPtr.Zero))
                    {
                        Trace.WriteLine("Failed to create hardlink for:" + targetFile);
                        File.Copy(files[i - 1], targetFile, true);
                    }
                }
                else
                {
                    File.Copy(files[i - 1], targetFile, true);
                }
                int percent = 50+ ((i * 100) / (files.Count * 2));
                UpdateStatus(percent);
            }
            if (cancel)
            {
                UpdateStatus(-1);
            }
            else
            {
                UpdateStatus(100);
                UpdateStatus(-2);
            }
            Debug.WriteLine("Exiting worker thread.");
        }

        void UpdateStatus(int progress)
        {
            if (InvokeRequired)
            {
                Invoke(new UpdateStatusDelegate(UpdateStatus), progress);
                return;
            }
            if (progress == -1)
            {
                workerThread = null;
                if (!terminating)
                {
                    MessageBox.Show("Pictures renaming cancelled.",
                        "Picture Arranger", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    progressBar1.Value = 0;
                    ControlsEnabled = true;    
                }
                else
                {
                    Application.Exit();
                }
            }
            else if (progress == -2)
            {
                workerThread = null;
                ControlsEnabled = true;
                MessageBox.Show("Pictures renamed & saved to output folder successfully.",
                    "Picture Arranger", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            else
            {
                progressBar1.Value = progress;
            }
        }

        DateTime? GetDates(string fileName)
        {
            Exception exception = null;
            using (Image image = Image.FromFile(fileName))
            {
                try 
                {
                    Debug.WriteLine("Processing: " + fileName);
                    var dateTaken = image.GetPropertyItem(0x9003);
                    if (null != dateTaken) {
                        string value = "";
                        foreach (char c in dateTaken.Value)
                            value += Convert.ToChar(c);
                        value = value.Remove(value.Length - 1, 1);

                        int spacePos = value.IndexOf(' ');
                        string dateString = value.Substring(0, spacePos);
                        string timeString = value.Substring(spacePos + 1, value.Length - 1);
                        string[] dateParts = dateString.Split(':');
                        string[] timeParts = timeString.Split(':');
                        DateTime dt = new DateTime
                            (Convert.ToInt32(dateParts[0].Trim()),
                             Convert.ToInt32(dateParts[1].Trim()),
                             Convert.ToInt32(dateParts[2].Trim()),
                             Convert.ToInt32(timeParts[0].Trim()),
                             Convert.ToInt32(timeParts[1].Trim()),
                             Convert.ToInt32(timeParts[2].Trim()));
                        return dt;
                    }
                    //foreach (var prop in image.PropertyItems)
                    //{
                    //    string value = prop.Value.ToString();
                    //    if (prop.Type == 2)
                    //    {
                    //        value = "";
                    //        foreach (char c in prop.Value)
                    //        {
                    //            value += Convert.ToChar(c);
                    //        }
                    //    }
                    //    Debug.WriteLine(String.Format("ID={0}, Value={1}, Name={2}", prop.Id, value, prop.Type));
                    //}
                }
                catch (ArgumentException e)
                {
                    exception = e;
                }
                catch (FormatException e2)
                {
                    exception = e2;
                }
            }
            if (null != exception)
            {
                //Debug.WriteLine("Failed: " + exception.Message);
            }
            if (processFilesWithoutDate)
            {
                if (usePictureFileModifiedTimeAsDefault)
                {
                    return ((new FileInfo(fileName)).LastWriteTime);
                }
                else
                {
                    return DateTime.Now;
                }
            }
            return null;
        }

        void FindFiles(DirectoryInfo dir)
        {
            foreach (var file in dir.GetFiles("*.*"))
            {
                if(ImageExtensions.Contains(Path.GetExtension(file.Name), StringComparer.OrdinalIgnoreCase))
                    _fileMap[file.FullName] = DateTime.Now;
            }

            if (!recurse) return;
            foreach (var d in dir.GetDirectories())
            {
                FindFiles(d);
            }
        }

        private void processPictureWithoutDateInfo_CheckedChanged(object sender, EventArgs e)
        {
            processFilesWithoutDate =
            defaultFileModifedDate.Enabled =
                defaultCurrentDate.Enabled = processPictureWithoutDateInfo.Checked;
        }

        private void PictureArranger_FormClosing(object sender, FormClosingEventArgs e)
        {
            terminating = true;
            cancel = true;

            if (null != workerThread)
            {
                e.Cancel = true;
            }
        }
    }
}
